﻿/*Copyright (c) 2012 Cesar Ramirez
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial 
 * portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MechTactics.GameElements;

namespace MechTactics
{
    public class Simulator
    {
        public List<Player> players;
        public TileMap map;
        public int mapSize;
        public int maxHeight;
        public int numberOfPlayers;
        public int idCount = 100;
        private AStarPathFinder pathfinder;
        public int STARTING_ORE = 1000;
        public int MAX_TURN = 1000;
        public int turnResult = 0;
        public bool readable = false;
        public bool running = false;
        public int[] action;

        public Simulator()
        {
            this.players = new List<Player>(0);
            this.action = new int[4];
        }

        public void addPlayer(Player player)
        {
            this.players.Add(player);
        }

        public void executeCommand(Command command)
        {
            if (command.objectId == -1)
                return;
            //turnResult = 0;
            int playerid = command.playerId;
            int objectid = command.objectId;
            GameObject gameObject = players.ElementAt(playerid).getObject(objectid);
            int x = command.objective.Key;
            int y = command.objective.Value;

            if (gameObject != null)
            {
                if (gameObject.getActive())
                {
                    gameObject.setActive(false);
                    if (gameObject.checkIfUnit())
                    {
                        //Unit action, move, attack or build
                        switch (command.type)
                        {
                            case 'X':
                                this.attack(gameObject, x, y);
                                break;
                            case 'Y':
                                Unit mover = (Unit)gameObject;
                                this.move(mover, x, y);
                                break;
                            default:
                                if (gameObject.getType() == 'L')
                                {
                                    int cost = -1;
                                    switch (command.type)
                                    {
                                        case 'B':
                                            cost = Constants.COST_MINE;
                                            break;
                                        case 'C':
                                            cost = Constants.COST_BARRACK;
                                            break;
                                        case 'D':
                                            cost = Constants.COST_FACTORY;
                                            break;
                                        case 'E':
                                            cost = Constants.COST_PORTAL;
                                            break;
                                        default:
                                            cost = -1;
                                            break;
                                    }
                                    if (cost > 0)
                                    {
                                        if (create(cost, gameObject, command.type, x, y))
                                        {
                                            players.ElementAt(gameObject.getTeam()).score += cost / 2;
                                        }
                                    }
                                }
                                break;
                        }
                    }
                    else
                    {
                        //Building action, create unit
                        if (map.isEmpty(x, y))
                        {
                            int cost = -1;
                            switch (gameObject.getType())
                            {
                                case 'C':
                                    switch (command.type)
                                    {
                                        case 'M':
                                            cost = Constants.COST_INFANTRY;
                                            break;
                                        case 'N':
                                            cost = Constants.COST_HINFANTRY;
                                            break;
                                        case 'O':
                                            cost = Constants.COST_SNIPER;
                                            break;
                                        default:
                                            break;
                                    }
                                    break;
                                case 'D':
                                    switch (command.type)
                                    {
                                        case 'P':
                                            cost = Constants.COST_LTANK;
                                            break;
                                        case 'Q':
                                            cost = Constants.COST_HTANK;
                                            break;
                                        case 'R':
                                            cost = Constants.COST_ARTILLERY;
                                            break;
                                        default:
                                            break;
                                    }
                                    break;
                                case 'E':
                                    switch (command.type)
                                    {
                                        case 'S':
                                            cost = Constants.COST_COMANDO;
                                            break;
                                        default:
                                            break;
                                    }
                                    break;
                            }
                            if (cost > 0)
                            {
                                if (create(cost, gameObject, command.type, x, y))
                                {
                                    players.ElementAt(gameObject.getTeam()).score += cost / 2;
                                }
                            }
                        }
                    }
                }
            }
        }

        private void move(Unit gameObject, int x, int y)
        {
            Path path = pathfinder.findPath(gameObject, gameObject.getMove(), x, y);
            if (path != null)
            {
                gameObject.setHasMoved(true);
                this.players.ElementAt(gameObject.getTeam()).move(gameObject.getId(), x, y);
            }
        }

        private void attack(GameObject gameObject,int x,int y)
        {
            int range_mod = 0;
            double armor_mod = 0.0;
            double health_mod = 0.0;
            if (x < 0 || x > map.size || y < 0 || y > map.size)
            {
                return;
            }
            if (map.terrain[x, y].z >= maxHeight - 1)
            {
                range_mod = 2;
            }
            else if (map.terrain[x, y].z <= 1)
            {
                armor_mod = 0.2;
            }

            if (map.isInRange(gameObject, x, y, range_mod) && !map.isEmpty(x, y))
            {
                GameObject objective = map.getObjectAt(x, y);

                if (!gameObject.checkIfUnit())
                {
                    armor_mod = 0.0;
                }
                health_mod = gameObject.getHealth() / 100f;
                double damage = (gameObject.getDamage() * health_mod) * (objective.getArmor() + armor_mod);
                map.terrain[x, y].element.setWasAttacked(true);
                if (map.terrain[x, y].element.doDamage(damage))
                {
                    objective.setHasMoved(true);
                    objective.setDead(true);
                    int score = 0;
                    switch (objective.getType())
                    {
                        case 'A':
                            players.ElementAt(objective.getTeam()).isActive = false;
                            turnResult = 1;
                            score = Constants.COST_BASE;
                            break;
                        case 'B':
                            score = Constants.COST_INFANTRY;
                            break;
                        case 'C':
                            score = Constants.COST_HINFANTRY;
                            break;
                        case 'D':
                            score = Constants.COST_SNIPER;
                            break;
                        case 'E':
                            score = Constants.COST_LTANK;
                            break;
                        case 'L':
                            score = Constants.COST_INFANTRY;
                            break;
                        case 'M':
                            score = Constants.COST_INFANTRY;
                            break;
                        case 'N':
                            score = Constants.COST_HINFANTRY;
                            break;
                        case 'O':
                            score = Constants.COST_SNIPER;
                            break;
                        case 'P':
                            score = Constants.COST_LTANK;
                            break;
                        case 'Q':
                            score = Constants.COST_HTANK;
                            break;
                        case 'R':
                            score = Constants.COST_ARTILLERY;
                            break;
                        case 'S':
                            score = Constants.COST_COMANDO;
                            break;
                    }
                    players.ElementAt(gameObject.getTeam()).score += score;
                }
                action[0] = gameObject.getX();
                action[1] = gameObject.getY();
                action[2] = x;
                action[3] = y;
            }
        }

        private bool create(int cost, GameObject gameObject, char commandType, int x, int y)
        {
            if ((players.ElementAt(gameObject.getTeam()).ore - cost) >= 0 && (players.ElementAt(gameObject.getTeam()).objectList.Count < 25))
            {
                if (map.isInRange(gameObject, x, y) && map.isEmpty(x, y))
                {
                    if (commandType == 'B' || commandType == 'C' || commandType == 'D' || commandType == 'E')
                    {
                        players.ElementAt(gameObject.getTeam()).createBuilding(idCount, commandType, x, y);
                        players.ElementAt(gameObject.getTeam()).ore -= cost; 
                        idCount++;
                        return true;
                    }
                    else
                    {
                        if (commandType == 'M' || commandType == 'N' || commandType == 'O' ||
                            commandType == 'P' || commandType == 'Q' || commandType == 'R' ||
                            commandType == 'S')
                        {
                            players.ElementAt(gameObject.getTeam()).createUnit(idCount, commandType, x, y);
                            players.ElementAt(gameObject.getTeam()).ore -= cost;
                            idCount++;
                            return true;
                        }
                    }

                }
            }
            return false;
        }

        public void flush()
        {
            for(int j = 0; j < players.Count; j++)
            {
                Player player = players.ElementAt(j);
                for (int i = 0; i < player.objectList.Count; i++)
                {
                    GameObject dead = player.objectList.ElementAt(i);
                    if (dead.isDead())
                        player.remove(dead.getId());
                }
                action[0] = -1;
                action[1] = -1;
                action[2] = -1;
                action[3] = -1;
            }

        }

        public void setMap(TileMap map, int maxHeight)
        {
            this.map = map;
            this.mapSize = map.size;
            this.maxHeight = maxHeight;
            this.pathfinder = new AStarPathFinder(map);
        }

        public void clearElements()
        {
            map.clearElements();
            for(int j = 0; j < players.Count; j++)
            {
                Player player = players.ElementAt(j);
                player.clearElements();
            }
        }

        public void clearMap()
        {
            map.clearElements();
        }
    }
}
